﻿using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text;
using System.Threading;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using BeanboxSoftware.BeanMap;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Collections;

namespace BeanboxSoftware.BeanMapTest
{
  [TestClass]
  public class SerializationTest
  {
    [TestMethod]
    public void GetEnumerator1KeyTest()
    {
      var map = new Map<int, string>();

      map[1] = "one";
      map[2] = "two";
      map[3] = "three";

      var kvs = ((IEnumerable<KeyValuePair<int, string>>)map).ToList();

      Assert.AreEqual(1, kvs[0].Key);
      Assert.AreEqual("one", kvs[0].Value);

      Assert.AreEqual(2, kvs[1].Key);
      Assert.AreEqual("two", kvs[1].Value);

      Assert.AreEqual(3, kvs[2].Key);
      Assert.AreEqual("three", kvs[2].Value);
    }

    [TestMethod]
    public void Add1KeyTest()
    {
      var map = new Map<int, string>();

      map.Add(1, "one");
      Assert.AreEqual("one", map[1]);
    }

    [TestMethod]
    public void GetEnumerator2KeyTest()
    {
      var map = new Map<int, int, string>();

      map[1, 1] = "one";
      map[2, 2] = "two";
      map[3, 3] = "three";

      var kvs = new List<KeyValuePair<int, IDictionary<int, string>>>();

      foreach (var kv in ((IDictionary<int, IDictionary<int, string>>)map))
      {
        kvs.Add(kv);
      }

      Assert.AreEqual(1, kvs[0].Key);
      Assert.AreEqual("one", kvs[0].Value[1]);

      Assert.AreEqual(2, kvs[1].Key);
      Assert.AreEqual("two", kvs[1].Value[2]);

      Assert.AreEqual(3, kvs[2].Key);
      Assert.AreEqual("three", kvs[2].Value[3]);
    }

    [TestMethod]
    public void GetEnumerator3KeyTest()
    {
      var map = new Map<int, int, int, string>();

      map[1, 1, 1] = "one";
      map[2, 2, 2] = "two";
      map[3, 3, 3] = "three";

      var kvs = new List<KeyValuePair<int, IDictionary<int, IDictionary<int, string>>>>();

      foreach (var kv in ((IDictionary<int, IDictionary<int, IDictionary<int, string>>>)map))
      {
        kvs.Add(kv);
      }

      Assert.AreEqual(1, kvs[0].Key);
      Assert.AreEqual("one", kvs[0].Value[1][1]);

      Assert.AreEqual(2, kvs[1].Key);
      Assert.AreEqual("two", kvs[1].Value[2][2]);

      Assert.AreEqual(3, kvs[2].Key);
      Assert.AreEqual("three", kvs[2].Value[3][3]);
    }

    private System.ServiceModel.ServiceHost _productHost;

    [TestMethod]
    public void WcfMap1KeyPassthroughSupportTest()
    {
      WcfPassthroughSupportHelper(svc =>
      {
        var map = new Map<string, double>();
        map["1.1"] = 1.1;

        var result = svc.Pass1KeyMap(map);
        Assert.IsTrue(result["1.1"] - 2.2 <= double.Epsilon);
      });
    }

    [TestMethod]
    public void WcfMap2KeyPassthroughSupportTest()
    {
      WcfPassthroughSupportHelper(svc =>
      {
        var map = new Map<string, int, double>();
        map["1.1", 1] = 1.1;

        var result = svc.Pass2KeyMap(map);
        Assert.IsTrue(result["1.1", 1] - 2.2 <= double.Epsilon);
      });
    }

    [TestMethod]
    public void WcfMap3KeyPassthroughSupportTest()
    {
      WcfPassthroughSupportHelper(svc =>
      {
        var map = new Map<string, int, char, double>();
        map["1.1", 1, 'c'] = 1.1;

        var result = svc.Pass3KeyMap(map);
        Assert.IsTrue(result["1.1", 1, 'c'] - 2.2 <= double.Epsilon);
      });
    }

    [Serializable]
    public class Cat
    {
      public string Name { get; set; }
      public string Notes { get; set; }
      public int Age { get; set; }

      public Cat()
      {
        Age = -1;
      }
    }

    [Serializable]
    public class Category
    {
      public Guid Id { get; set; }
      public string Notes { get; set; }

      public override bool Equals(object obj)
      {
        var tht = obj as Category;
        if (tht == null)
          return false;

        return Id.Equals(tht.Id) && (Notes ?? "").Equals(tht.Notes ?? "");
      }

      public override int GetHashCode()
      {
        return Id.GetHashCode() ^ (Notes ?? "").GetHashCode();
      }
    }

    [TestMethod]
    public void WcfMap3KeyPassthroughCustomTypeSupportTest()
    {
      WcfPassthroughSupportHelper(svc =>
      {
        var cats = new Map<Category, string, Cat>();
        var category1 = new Category { Id = Guid.NewGuid(), Notes = "notes about guid one" };

        var fifi = new Cat { Name = "Fifi", Notes = "2" };
        var boo = new Cat { Name = "Boo", Notes = "3" };

        var boo_jr = new Cat { Name = "boo_jr", Notes = "0" };
        var fifi_jr = new Cat { Name = "fifi_jr", Notes = "1" };

        cats[category1, fifi.Name] = fifi;
        cats[category1, boo.Name] = boo;
        cats[category1, boo_jr.Name] = boo_jr;
        cats[category1, fifi_jr.Name] = fifi_jr;

        var category2 = new Category { Id = Guid.NewGuid(), Notes = "notes about guid two" };
        var kitty = new Cat { Name = "Kitty", Notes = "4" };
        cats[category1, kitty.Name] = kitty;

        var result = svc.CatTest(cats);

        // for sanity of the test case, confirm the given values were not changed 
        Assert.AreEqual(-1, fifi.Age);
        Assert.AreEqual(-1, boo.Age);
        Assert.AreEqual(-1, boo_jr.Age);
        Assert.AreEqual(-1, fifi_jr.Age);
        Assert.AreEqual(-1, kitty.Age);

        // verify the data was modified
        Assert.AreEqual(2, result[category1, fifi.Name].Age);
        Assert.AreEqual(3, result[category1, boo.Name].Age);
        Assert.AreEqual(0, result[category1, boo_jr.Name].Age);
        Assert.AreEqual(1, result[category1, fifi_jr.Name].Age);
        Assert.AreEqual(4, result[category1, kitty.Name].Age);
      });
    }

    [TestMethod]
    public void Wcf1KeyMapDefaultValueBehaviorPassthroughTest()
    {
      WcfPassthroughSupportHelper(svc =>
      {
        var map = new Map<string, double>();
        map.DefaultValue = 1.3;

        var result = svc.Pass1KeyMap(map);
        Assert.AreEqual(1.3, result["hi"]);
        Assert.IsFalse(result.ContainsKey("hi"));
      });
    }

    [TestMethod]
    public void Wcf2KeyMapDefaultValueBehaviorPassthroughTest()
    {
      WcfPassthroughSupportHelper(svc =>
      {
        var map = new Map<string, int, double>();
        map.DefaultValue = 1.3;

        var result = svc.Pass2KeyMap(map);
        Assert.AreEqual(1.3, result["hi", 1]);
        Assert.IsFalse(result.ContainsKey("hi"));
        Assert.IsFalse(result.ContainsKey("hi", 1));
      });
    }

    [TestMethod]
    public void Wcf3KeyMapDefaultValueBehaviorPassthroughTest()
    {
      WcfPassthroughSupportHelper(svc =>
      {
        var map = new Map<string, int, char, double>();
        map.DefaultValue = 1.3;

        var result = svc.Pass3KeyMap(map);
        Assert.AreEqual(1.3, result["", 1, 'c']);
      });
    }

    [TestMethod]
    public void DefaultGenerationNotSupportedThroughSerializationTest()
    {
      WcfPassthroughSupportHelper(svc =>
      {
        var map = new Map<string, int, char, double>();

        map.KeyNotFoundBehavior = KeyNotFoundBehavior.DefaultGeneration;
        TestUtils.AssertExceptionThrown<NullReferenceException>(() => { double d = map["", 2, 'a']; });

        map.DefaultGeneration = (k1, k2, k3) => 4.1;

        var result = svc.Pass3KeyMap(map);

        TestUtils.AssertExceptionThrown<NotSupportedException>(() => { double d = result["", 1, 'c']; });

        result.DefaultGeneration = (k1, k2, k3) => 3.4;
        Assert.AreEqual(3.4, result["foo", 100, 'g']);
      });
    }

    [TestMethod]
    public void DataContractTest()
    {
      var map = new Map<string, int, char, double>();
      map["1.1", 1, 'c'] = 1.1;
      SerializationTester(map);
    }

    [TestMethod]
    public void CompressionSerializationTest()
    {
      var map = new Map<string, int, char, double>();
      map["1.1", 1, 'c'] = 1.1;
      var data = CompressingSerializer.Compress(map);
      var result = CompressingSerializer.Decompress<Map<string, int, char, double>>(data);
      Assert.AreEqual(1.1, result["1.1", 1, 'c']);
    }

    [TestMethod]
    public void CompressionNestedSerializationTest()
    {
      var map = new Map<string, int, char, Map<string, int, double>>();
      var value = new Map<string, int, double>();
      value["test", 44] = 2.24;
      map["1.1", 1, 'c'] = value;

      map.DefaultValue = null;
      value.DefaultValue = 5.45;

      var data = CompressingSerializer.Compress(map);
      var result = CompressingSerializer.Decompress<Map<string, int, char, Map<string, int, double>>>(data);

      Assert.AreEqual(2.24, result["1.1", 1, 'c']["test", 44]);
      Assert.IsNull(result["foo", 4, 'w']);
      Assert.AreEqual(5.45, result["1.1", 1, 'c']["does not exist", 44]);
    }

    [TestMethod]
    public void WcfNestedSerializationTest()
    {
      WcfPassthroughSupportHelper(svc =>
      {
        var map = new Map<string, int, char, Map<string, int, double>>();
        var value = new Map<string, int, double>();
        value["test", 44] = 2.24;
        map["1.1", 1, 'c'] = value;

        map.DefaultValue = null;
        value.DefaultValue = 5.45;

        var result = svc.NestedTest(map);

        Assert.AreEqual(2.24, result["1.1", 1, 'c']["test", 44]);
        Assert.IsNull(result["foo", 4, 'w']);
        Assert.AreEqual(5.45, result["1.1", 1, 'c']["does not exist", 44]);
      });
    }
    
    [System.ServiceModel.ServiceContract]
    interface ICustomBeanMapService
    {
      [System.ServiceModel.OperationContract]
      Map<string, double> Pass1KeyMap(Map<string, double> map);

      [System.ServiceModel.OperationContract]
      Map<string, int, double> Pass2KeyMap(Map<string, int, double> map);

      [System.ServiceModel.OperationContract]
      Map<string, int, char, double> Pass3KeyMap(Map<string, int, char, double> map);

      [System.ServiceModel.OperationContract]
      Map<Category, string, Cat> CatTest(Map<Category, string, Cat> map);
    
      [System.ServiceModel.OperationContract]
      Map<string, int, char, Map<string, int, double>> NestedTest(Map<string, int, char, Map<string, int, double>> map);
    }

    private void WcfPassthroughSupportHelper(Action<ICustomBeanMapService> action)
    {
      GenericWcfPassthroughSupportHelper<CustomBeanMapService, ICustomBeanMapService>(action);
    }

    private void GenericWcfPassthroughSupportHelper<TService, TInf>(Action<TInf> action)
    {
      try
      {
        StartWcfHost(typeof(TService), typeof(TInf));

        VerifyWcfPassage<TInf>(action);
      }
      catch (System.ServiceModel.EndpointNotFoundException)
      {
        Assert.Fail("This test is most likely failing because you need to allow the QTAgent32 through the firewall. You'll find it at (visual studio install path)\\common7\\ide\\qtagent32.exe");
      }
      catch (Exception ex)
      {
        throw;
      }
      finally
      {
        StopWcfHost();
      }
    }

    private void VerifyWcfPassage<TInf>(Action<TInf> action)
    {
      System.ServiceModel.ChannelFactory<TInf> myChannelFactory = null;

      try
      {
        int port = _productHost.Description.Endpoints[0].ListenUri.Port;

        // This code is written by an application developer.
        // Create a channel factory.
        var myBinding = new System.ServiceModel.NetTcpBinding();

        var myEndpoint = new System.ServiceModel.EndpointAddress(string.Format("net.tcp://localhost:{0}/{1}", port, typeof(TInf).Name));

        myChannelFactory = new System.ServiceModel.ChannelFactory<TInf>(myBinding, myEndpoint);

        // Create a channel.
        var channel = myChannelFactory.CreateChannel();
        action(channel);
      }
      finally
      {
        try
        {
          if (myChannelFactory != null)
          {
            myChannelFactory.Close();
          }
        }
        catch
        {
        }
      }
    }

    class CustomBeanMapService : ICustomBeanMapService
    {
      public Map<string, double> Pass1KeyMap(Map<string, double> map)
      {
        if (map.Count > 0)
        {
          var keys = map.KeyValues.ToList();

          foreach (var kv in keys)
          {
            map[kv.Key] *= 2;
          }
        }

        return map;
      }

      public Map<string, int, double> Pass2KeyMap(Map<string, int, double> map)
      {
        var keys = map.KeyValues.ToList();

        foreach (var kv in keys)
        {
          map[kv.Key.Item1, kv.Key.Item2] *= 2;
        }

        return map;
      }

      public Map<string, int, char, double> Pass3KeyMap(Map<string, int, char, double> map)
      {
        var keys = map.KeyValues.ToList();

        foreach (var kv in keys)
        {
          map[kv.Key.Item1, kv.Key.Item2, kv.Key.Item3] *= 2;
        }

        return map;
      }

      public Map<Category, string, Cat> CatTest(Map<Category, string, Cat> map)
      {
        foreach (var keySet in map.Keys)
        {
          var cat = map[keySet.Item1, keySet.Item2];
          cat.Age = int.Parse(cat.Notes);
        }

        return map;
      }


      public Map<string, int, char, Map<string, int, double>> NestedTest(Map<string, int, char, Map<string, int, double>> map)
      {
        return map;
      }
    }

    private void StartWcfHost(Type serviceType, Type serviceContractType)
    {
      _productHost = null;

      string url = "net.tcp://localhost/" + serviceContractType.Name;
      var myBinding = new System.ServiceModel.NetTcpBinding();

      _productHost = new System.ServiceModel.ServiceHost(serviceType, new Uri(url));
      _productHost.AddServiceEndpoint(serviceContractType, myBinding, url);

      _productHost.Open();
    }

    private void StopWcfHost()
    {
      if (_productHost != null)
      {
        try
        {
          _productHost.Close();
        }
        catch
        {
        }
      }
    }

    private static void SerializationTester(Object element)
    {
      String text = DataContractSerializingUtility.TestContractObject(element);
      Assert.IsNotNull(text);
    }

    private static class DataContractSerializingUtility
    {
      public static string TestContractObject<T>(T obj)
      {
        var dataContractSerializer = new NetDataContractSerializer();
        var builder = new StringBuilder();

        try
        {
          using (var memoryStream = new MemoryStream())
          {
            dataContractSerializer.WriteObject(memoryStream, obj);
            builder.Append(memoryStream.GetBuffer());
          }
        }
        catch (Exception e)
        {
          //See what went wrong.
          throw;
        }
        return builder.ToString();
      }
    }

    public static class CompressingSerializer
    {
      private static MemoryStream ObjectToMemoryStream(object toSerialize)
      {
        using (MemoryStream stream = new MemoryStream())
        {
          BinaryFormatter bFormatter = new BinaryFormatter();
          bFormatter.Serialize(stream, toSerialize);
          stream.Close();
          return stream;
        }
      }

      public static T DeserializeFromBytes<T>(MemoryStream serializedStream)
      {
        BinaryFormatter bFormatter = new BinaryFormatter();
        T returnObject = (T)bFormatter.Deserialize(serializedStream);
        serializedStream.Close();
        return returnObject;
      }

      public static byte[] SerializeToBytes(object toSerialize)
      {
        return ObjectToMemoryStream(toSerialize).ToArray();
      }

      public static T DeserializeFromBytes<T>(byte[] serializedObject)
      {
        using (MemoryStream memoryStream = new MemoryStream(serializedObject))
        {
          return DeserializeFromBytes<T>(memoryStream);
        }
      }

      public static byte[] Compress(object toCompress)
      {
        var bytes = SerializeToBytes(toCompress);

        using (MemoryStream memory = new MemoryStream())
        {
          using (DeflateStream zip = new DeflateStream(memory, CompressionMode.Compress, true))
          {
            var formatter = new DataContractSerializer(typeof(byte[]));
            formatter.WriteObject(zip, bytes);
          } return memory.ToArray();
        }
      }

      public static T Decompress<T>(byte[] compressedData)
      {
        using (MemoryStream memory = new MemoryStream(compressedData))
        {
          using (DeflateStream zip = new DeflateStream(memory, CompressionMode.Decompress, true))
          {
            var formatter = new DataContractSerializer(typeof(byte[]));
            var returnedBytes = (byte[])formatter.ReadObject(zip);
            return DeserializeFromBytes<T>(returnedBytes);
          }
        }
      }
    }
  }
}
